import { $set } from '@feng3d/serialization';
import { Color, DepthTexture3D, MeshBasicMaterial3D, Node3D, OrthographicCamera3D, PerspectiveCamera3D, PixelFormat, PlaneGeometry3D, Scene3D, ShaderMaterial3D, StatsComponent, TextureDataType, TorusKnotGeometry3D, WebGLRenderer3D } from 'feng3d-three';
import { WebGLRenderTarget3D } from 'feng3d-three';
import GUI from 'lil-gui';
import '../../extension/controls/OrbitControls3D';
import { OrbitControls3D } from '../../extension/controls/OrbitControls3D';

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;
let controls: OrbitControls3D;
let stats: StatsComponent;
let target: WebGLRenderTarget3D;
let postScene: Scene3D;
let postCamera: OrthographicCamera3D;
let postMaterial: ShaderMaterial3D;
let supportsExtension = true;

const params: { format: PixelFormat, type: TextureDataType } = {
    format: 'DepthFormat',
    type: 'UnsignedShortType'
};

const formats: { [names: string]: PixelFormat } = { DepthFormat: 'DepthFormat', DepthStencilFormat: 'DepthStencilFormat' };
const types: { [names: string]: TextureDataType } = { UnsignedShortType: 'UnsignedShortType', UnsignedIntType: 'UnsignedIntType', UnsignedInt248Type: 'UnsignedInt248Type' };

init();
animate();

function init()
{
    renderer = new Node3D().addComponent('WebGLRenderer3D');

    if (renderer.capabilities.isWebGL2 === false && renderer.extensions.has('WEBGL_depth_texture') === false)
    {
        supportsExtension = false;
        document.querySelector('#error')['style'].display = 'block';

        return;
    }

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    //

    stats = renderer.addComponent('StatsComponent');
    document.body.appendChild(stats.dom);

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 70, aspect: window.innerWidth / window.innerHeight, near: 0.01, far: 50 });
    camera.entity.position.z = 4;

    controls = renderer.addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
    controls.enableDamping = true;

    // Create a render target with depth texture
    setupRenderTarget();

    // Our scene
    setupScene();

    // Setup post-processing step
    setupPost();

    onWindowResize();
    window.addEventListener('resize', onWindowResize);

    //
    const gui = new GUI({ width: 300 });

    gui.add(params, 'format', formats).onChange(setupRenderTarget);
    gui.add(params, 'type', types).onChange(setupRenderTarget);
    gui.open();
}

function setupRenderTarget()
{
    if (target) target.dispose();

    const format = params.format;
    const type = params.type;

    target = new WebGLRenderTarget3D(window.innerWidth, window.innerHeight);
    target.texture.minFilter = 'NearestFilter';
    target.texture.magFilter = 'NearestFilter';
    target.stencilBuffer = (format === 'DepthStencilFormat');
    target.depthTexture = new DepthTexture3D();
    target.depthTexture.format = format;
    target.depthTexture.type = type;
}

function setupPost()
{
    // Setup post processing stage
    postCamera = new Node3D().addComponent('OrthographicCamera3D', { left: -1, right: 1, top: 1, bottom: -1, near: 0, far: 1 });
    postMaterial = $set(new ShaderMaterial3D(), {
        vertexShader: document.querySelector('#post-vert').textContent.trim(),
        fragmentShader: document.querySelector('#post-frag').textContent.trim(),
        uniforms: {
            cameraNear: { value: camera.near },
            cameraFar: { value: camera.far },
            tDiffuse: { value: null },
            tDepth: { value: null }
        }
    });
    const postPlane = $set(new PlaneGeometry3D(), { width: 2, height: 2 });
    const postQuad = new Node3D().addComponent('Mesh3D', { geometry: postPlane, material: postMaterial });
    postScene = new Node3D().addComponent('Scene3D');
    postScene.entity.addChild(postQuad.entity);
}

function setupScene()
{
    scene = renderer.addComponent('Scene3D');

    const geometry = $set(new TorusKnotGeometry3D(), { radius: 1, tube: 0.3, tubularSegments: 128, radialSegments: 64 });
    const material = $set(new MeshBasicMaterial3D(), { color: new Color('blue') });

    const count = 50;
    const scale = 5;

    for (let i = 0; i < count; i++)
    {
        const r = Math.random() * 2.0 * Math.PI;
        const z = (Math.random() * 2.0) - 1.0;
        const zScale = Math.sqrt(1.0 - z * z) * scale;

        const mesh = new Node3D().addComponent('Mesh3D', { geometry, material });
        mesh.entity.position.set(
            Math.cos(r) * zScale,
            Math.sin(r) * zScale,
            z * scale
        );
        mesh.entity.rotation.set(Math.random(), Math.random(), Math.random());
        scene.entity.addChild(mesh.entity);
    }
}

function onWindowResize()
{
    const aspect = window.innerWidth / window.innerHeight;
    camera.aspect = aspect;
    camera.updateProjectionMatrix();

    const dpr = renderer.getPixelRatio();
    target.setSize(window.innerWidth * dpr, window.innerHeight * dpr);
    renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate()
{
    if (!supportsExtension) return;

    requestAnimationFrame(animate);

    // render scene into target
    renderer.setRenderTarget(target);
    renderer.render(scene, camera);

    // render post FX
    postMaterial.uniforms.tDiffuse.value = target.texture._texture;
    postMaterial.uniforms.tDepth.value = target.depthTexture._texture;

    renderer.setRenderTarget(null);
    renderer.render(postScene, postCamera);

    controls.update(); // required because damping is enabled

    stats.update();
}
